#include "buffer.h"
#include "objpool.h"
#include <memory.h>

struct bufferFactory* bufferFactory_build(size_t buffer_len, size_t count) {
	size_t factory_size = sizeof(struct bufferFactory);
	struct bufferFactory *factory = (struct bufferFactory *) malloc(
			factory_size);

	size_t buffer_size = sizeof(struct buffer);
	struct obj_pool *buffers = objpool_build(buffer_size, count);

	struct obj_pool *bytes = objpool_build(buffer_len, count);

	factory->buffer_pool = buffers;
	factory->bytes_pool = bytes;
	return factory;
}

int bufferFactory_release(struct bufferFactory *factory) {
	objpool_release(factory->bytes_pool);
	objpool_release(factory->buffer_pool);
	free(factory);
	return 0;
}

struct buffer* buffer_new(struct bufferFactory *factory) {
	struct buffer *buffer_hdl = (struct buffer *) object_new(
			factory->buffer_pool);
	char *bytes = (char *) object_new(factory->bytes_pool);

	buffer_hdl->data = bytes;
	buffer_hdl->length = factory->bytes_pool->unit;
	buffer_hdl->index = 0;
	buffer_hdl->limit = buffer_hdl->length;
	buffer_hdl->next = NULL;

	buffer_hdl->formatPos = -1;
	buffer_hdl->formatSize = 2;
	return buffer_hdl;
}

int buffer_del(struct bufferFactory *factory, struct buffer *buffer) {
	int ret_a = object_del(factory->bytes_pool, buffer->data);
	int ret_b = object_del(factory->buffer_pool, buffer);
	return ret_a | ret_b;
}

/**
 * write data to file(socket)
 **/
void ready_to_write(struct buffer *buff) {
	buff->limit = buff->index;
	buff->index = 0;
}

/**
 * read data from file (socket)
 **/
void ready_to_read(struct buffer *buff) {
	buff->index = 0;
	buff->limit = buff->length;
}

int bytes_to_byte(struct buffer *pinput, char *value, int is_big) {
	if (pinput->index + 1 > pinput->limit) {
		return -1;
	}
	*value = pinput->data[pinput->index];
	pinput->index++;
	return 0;
}

int byte_to_bytes(struct buffer *poutput, char value, int is_big) {
	if (poutput->index + 1 > poutput->limit) {
		return -1;
	}
	poutput->data[poutput->index] = value;
	poutput->index++;
	return 0;
}

int bytes_to_short(struct buffer *pinput, u_int16_t *value, int is_big) {
	if ((pinput->index + 2) > pinput->limit) {
		return -1;
	}
	u_int32_t idx = pinput->index;
	u_int16_t out = 0x0000;
	if (is_big) {
		out = (pinput->data[idx] << 8) | out;
		out = (pinput->data[idx + 1]) | out;
	} else {
		out = (pinput->data[idx]) | out;
		out = (pinput->data[idx + 1] << 8) | out;
	}
	*value = out;
	pinput->index += 2;
	return 0;
}

int short_to_bytes(struct buffer *poutput, u_int16_t value, int is_big) {
	if ((poutput->index + 2) > poutput->limit) {
		return -1;
	}
	u_int32_t idx = poutput->index;

	if (is_big) {
		poutput->data[idx] = (char) (value >> 8);
		poutput->data[idx + 1] = (char) value;
	} else {
		poutput->data[idx] = (char) value;
		poutput->data[idx + 1] = (char) (value >> 8);
	}
	poutput->index += 2;
	return 0;
}

int bytes_to_int(struct buffer *pinput, u_int32_t *value, int is_big) {
	if ((pinput->index + 4) > pinput->limit) {
		return -1;
	}
	u_int32_t idx = pinput->index;
	u_int32_t out = 0x00000000;
	if (is_big) {
		out = (pinput->data[idx] << 24) | out;
		out = (pinput->data[idx + 1] << 16) | out;
		out = (pinput->data[idx + 2] << 8) | out;
		out = (pinput->data[idx + 3]) | out;
	} else {
		out = (pinput->data[idx]) | out;
		out = (pinput->data[idx + 1] << 8) | out;
		out = (pinput->data[idx + 2] << 16) | out;
		out = (pinput->data[idx + 3] << 24) | out;
	}
	*value = out;
	pinput->index += 4;

	return 0;
}
int int_to_bytes(struct buffer *poutput, u_int32_t value, int is_big) {
	if ((poutput->index + 4) > poutput->limit) {
		return -1;
	}
	u_int32_t idx = poutput->index;

	if (is_big) {
		poutput->data[idx] = (char) (value >> 24);
		poutput->data[idx + 1] = (char) (value >> 16);
		poutput->data[idx + 2] = (char) (value >> 8);
		poutput->data[idx + 3] = (char) value;
	} else {
		poutput->data[idx] = (char) value;
		poutput->data[idx + 1] = (char) (value >> 8);
		poutput->data[idx + 2] = (char) (value >> 16);
		poutput->data[idx + 3] = (char) (value >> 24);
	}
	poutput->index += 4;

	return 0;
}

int bytes_to_str(struct buffer *pinput, char **value, int is_big) {
	u_int16_t len = 0;

	int ret = bytes_to_short(pinput, &len, is_big);
	if (ret < 0) {
		return ret;
	}
	u_int32_t idx = pinput->index;

	if ((idx + len) > pinput->limit) {
		return -1;
	}
	char *str = (char *) malloc(len + 1);
	memcpy(str, pinput->data + idx, len);
	str[len] = '\0';
	*value = str;
	pinput->index = idx + len;
	return 0;
}

int str_to_bytes(struct buffer *poutput, char *value, int is_big) {
	size_t len = strlen(value);
	u_int32_t idx = poutput->index;
	if ((idx + len + 2) > poutput->limit) {
		return -1;
	}
	short_to_bytes(poutput, (u_int16_t) len, is_big);
	idx = poutput->index;
	memcpy(poutput->data + idx, value, len);
	poutput->index += len;
	return 0;
}

int buffer_skip(struct buffer *pbuffer, unsigned int count) {
	u_int32_t idx = pbuffer->index;

	if ((idx + count) > pbuffer->limit) {
		return -1;
	}
	pbuffer->index += count;
	return 0;
}

int buffer_begin_batch(struct buffer *pbuffer, unsigned int formatSize) {
	if (formatSize != 1 && formatSize != 2 && formatSize != 4) {
		return -1;
	}

	u_int32_t idx = pbuffer->index;
	if ((idx + formatSize) > pbuffer->limit) {
		return -1;
	}
	pbuffer->formatSize = formatSize;
	pbuffer->formatPos = pbuffer->index;
	buffer_skip(pbuffer, formatSize);
	return 0;
}

int buffer_end_batch(struct buffer *pbuffer, unsigned int batchCount,
		int is_big) {

	if (pbuffer->formatSize == 1) {
		pbuffer->data[pbuffer->formatPos] = (char) batchCount;
	} else if (pbuffer->formatSize == 2) {
		u_int32_t idx = pbuffer->formatPos;
		if (is_big) {
			pbuffer->data[idx] = (char) (batchCount >> 8);
			pbuffer->data[idx + 1] = (char) batchCount;
		} else {
			pbuffer->data[idx] = (char) batchCount;
			pbuffer->data[idx + 1] = (char) (batchCount >> 8);
		}
	} else if (pbuffer->formatSize == 4) {
		u_int32_t idx = pbuffer->formatPos;

		if (is_big) {
			pbuffer->data[idx] = (char) (batchCount >> 24);
			pbuffer->data[idx + 1] = (char) (batchCount >> 16);
			pbuffer->data[idx + 2] = (char) (batchCount >> 8);
			pbuffer->data[idx + 3] = (char) batchCount;
		} else {
			pbuffer->data[idx] = (char) batchCount;
			pbuffer->data[idx + 1] = (char) (batchCount >> 8);
			pbuffer->data[idx + 2] = (char) (batchCount >> 16);
			pbuffer->data[idx + 3] = (char) (batchCount >> 24);
		}
	} else {
		return -1;
	}
	return 0;
}

int bytes_to_barray(struct buffer *pinput, char *value, size_t arr_len) {
	if ((pinput->index + arr_len) > pinput->limit) {
		return -1;
	}
	memcpy(value, pinput->data + pinput->index, arr_len);
	pinput->index += arr_len;
	return 0;
}
int barray_to_bytes(struct buffer *poutput, char *value, size_t arr_len) {
	if ((poutput->index + arr_len) > poutput->limit) {
		return -1;
	}
	memcpy(poutput->data + poutput->index, value, arr_len);
	poutput->index += arr_len;
	return 0;
}

void buffer_compact(struct buffer *buff) {
	u_int32_t offset = buff->index;
	u_int32_t length = buff->limit - offset;
	memmove(buff->data, (buff->data + offset), (size_t) length);
	buff->index = offset;
	buff->limit = buff->length;
}
